﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Xml;

namespace Sinacor.Infra.Common.Serialization
{
    /// <summary>
    /// Classe com métodos para criar um cópia de um objeto
    /// </summary>
    public class CloneHelper
    {
        protected CloneHelper()
        { 
        }

        public static event ResolveEventHandler AssemblyResolve;

        /// <summary>
        /// Cria uma cópia completa de um objeto
        /// </summary>
        /// <param name="obj">Objeto a ser clonado</param>
        /// <returns>Instância do objeto clonado</returns>
        public static object Clone(object obj)
        {
            if (obj == null)
                throw new ArgumentNullException("obj");

            ISerializer<byte[]> serializer = null;
            object clonedObj = null;
            byte[] binObj = null;

            lock (obj)
            {
                if (AssemblyResolve == null)
                {
                    serializer = new BinarySerializer();
                }
                else
                {
                    serializer = new BinarySerializer(AssemblyResolve);
                }                

                // Serializando
                binObj = serializer.Serialize(obj);

                // Deserializando
                clonedObj = serializer.Deserialize(binObj);
            }

            return clonedObj;
        }



        /// <summary>
        /// Cria uma cópia de um objeto, copiando somente as properties públicas
        /// </summary>
        /// <param name="obj">Objeto a ser clonado</param>
        /// <returns>Instância do objeto clonado</returns>
        public static object ShallowClone(object obj)
        {
            if (obj == null)
                throw new ArgumentNullException("obj");

            // Criando nova instancia do objeto
            object clonedObj = Activator.CreateInstance(obj.GetType());

            // Percorrendo todas as properties publicas do objeto
            foreach (PropertyInfo prop in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance))
            {
                // Verifica se property é read-write e se os valores são diferentes
                if (prop.CanRead && prop.CanWrite)
                {
                    object clonedValue = clonedObj.GetType().GetProperty(prop.Name).GetValue(clonedObj, null);
                    object value = prop.GetValue(obj, null);

                    bool iguais = false;

                    if (clonedValue is IComparable && value is IComparable &&
                        ((IComparable)clonedValue).CompareTo((IComparable)value) == 0)
                    {
                        iguais = true;
                    }
                    else if (clonedValue == value)
                    {
                        iguais = true;
                    }

                    if (!iguais)
                    {
                        // Copiando valor da property
                        clonedObj.GetType().GetProperty(prop.Name).SetValue(clonedObj, value, null);
                    }
                }
            }

            return clonedObj;
        }
    }
}
